﻿using System;
using System.Linq;
using libEngineAI;
using libEngineAI.Interfaces;
using System.Collections.Generic;
using libGameCheckers.Boards;
using System.Collections;

using System.Diagnostics;

namespace libGameCheckers
{
	public class Checkers : IGame
	{
		public Coordinate MaxCoordinates { get; set; }

		#region IGame implementation

		public IBoard Board { get; set; }

		public IEnumerable<IPiece> Pieces { get; set; }

		public TeamEnum Turn { get; set; }

		public IPlayer WhitePlayer { get; set; }
		public IPlayer BlackPlayer { get; set; } 

		public void Initialize()

		{
			if( MaxCoordinates.x <= 4 || MaxCoordinates.y <= 4)
				throw new ArgumentException( "Max Coordinates is not initialize correctly. >= 4" );

			Turn = TeamEnum.WHITE;
			_MovesToDraw = 20;

			Pieces = new List<IPiece>();

			for( int j = 0; j < MaxCoordinates.y; ++j )
			{
				for( int i = 0; i < MaxCoordinates.x; ++i )
				{
					if( (i + j) % 2 != 0 && (j < 3 || j >= MaxCoordinates.y - 3) )
					{ 
						IPiece p = new Piece() 
						{
							Coordinates = new Coordinate() { x = i, y = j },
							Team = j < (MaxCoordinates.y / 2) ? TeamEnum.BLACK : TeamEnum.WHITE,
							Value = (int) PieceType.MAIDEN
						};

						((List<IPiece>)Pieces).Add( p );
					}
				}
			}

			//GenerateMoves();
			Board.Refresh();
		}

		public void Start()
		{ 
			// TODO: Matizar este bucle un poco
			TeamEnum winner = TeamEnum.NONE;

			do
			{
				Movement mov = null;

				switch( Turn )
				{
					case TeamEnum.WHITE:
						mov = WhitePlayer.GetMovement( this );
						break;

					case TeamEnum.BLACK:
						mov = BlackPlayer.GetMovement( this );
						break;

					default:
						break;
				}

				if( mov != null )
				{
					DoMove( mov );

					ChangeTurn();
					Board.Refresh();

					if( mov.Eat )
						_MovesToDraw = 20;
					else
						_MovesToDraw--;

					//
					System.Threading.Thread.Sleep( 750);
				}

				if( _MovesToDraw == 0)
				{
					Board.ShowWinner( "TABLAS!!" );
					break;
				}

				winner = Winner();
			} while( winner == TeamEnum.NONE );

			if( winner != TeamEnum.NONE)
				Board.ShowWinner( String.Format( "Ganador: {0}", ( winner == TeamEnum.BLACK ) ? "NEGRAS" : "BLANCAS" ) );
		}

		public IEnumerable<Movement> GenerateMoves()
		{			
			if( _NeedToGenerateMoves )
			{
				_Moves.Clear();

				foreach( IPiece p in Pieces.Where( a => a.Team == Turn ))
				{
					// It has no sense generate moves if its not the turn
					if( ( _UniqueForMove == null && !p.IsDeath ) || ( _UniqueForMove != null && p.Equals( _UniqueForMove ) ) )
						_Moves.AddRange( SetPosibleMoves( p ) );
				}

				// If exist eat-move, non-eat-moves are deleted
				if( _Moves.Exists( m => m.Eat ) )
					_Moves.RemoveAll( mov => !mov.Eat );
		
				_NeedToGenerateMoves = false;
			}
				
			List<Movement> clonado = new List<Movement>();
			foreach( Movement m in _Moves )
				clonado.Add( m.Clone() );

			//return _Moves;
			return clonado;
			//foreach( Movement m in _Moves )
			//	yield return  m.Clone();
		}

		public void DoMove( Movement m)
		{
			/* Suposed is a valid movement */

			//IPiece p = Pieces.First( a => a.Equals( m.PieceMoved ) );
			IPiece p = m.PieceMoved;
			//IPiece p = Pieces.First( a => !a.IsDeath && a.Coordinates.Equals( m.Begin ) );

			if( m.TransformationMovement )
			{
				m.PastValue = p.Value;
				p.Value = (int) PieceType.QUEEN;
			}

			if( m.Eat )
			{
				//IPiece eated = Pieces.First( a => a.Equals( m.PieceEated ) );
				//eated.IsDeath = true;
				//IPiece eated = Pieces.First( a => !a.IsDeath && a.Coordinates.Equals( m.PieceEatedCoords ) );
				m.PieceEated.IsDeath = true;
				//eated.IsDeath = true;
				_lastMovement = m;
			}
			else
			{
				_lastMovement = null;
				_UniqueForMove = null;
			}

			p.Coordinates = m.End;

			_NeedToGenerateMoves = true;
			_NeedtoCalcWinner = true;
		}

		public void UndoMove( Movement m)
		{
			//IPiece p = Pieces.First( a => !a.IsDeath && a.Coordinates.Equals( m.End ) );
			IPiece p = m.PieceMoved;

			if( m.TransformationMovement )
				p.Value = m.PastValue;

			if( m.Eat )
			{
				m.PieceEated.IsDeath = false;
				//IPiece eated = Pieces.First( a => a.IsDeath && a.Coordinates.Equals( m.PieceEatedCoords ) );
				//eated.IsDeath = false;
			}

			p.Coordinates = m.Begin;

			_NeedToGenerateMoves = true;
			_NeedtoCalcWinner = true;
			_lastMovement = null;
		}
			
		public TeamEnum ChangeTurn()
		{
			bool change_turn = true;

			if( _lastMovement != null && _lastMovement.Eat && !_lastMovement.TransformationMovement )
			{
				//_lastMovement.PieceMoved.PossibleMoves.Clear();
				//IPiece lastPieceMoved = Pieces.First( a => !a.IsDeath && a.Coordinates.Equals( _lastMovement.End ) );
				IPiece lastPieceMoved = _lastMovement.PieceMoved;
				IEnumerable<Movement> moves_for_piece = SetPosibleMoves( lastPieceMoved );
				//if( _lastMovement.PieceMoved.PossibleMoves.Exists( m => m.Eat ) )
				if( moves_for_piece.FirstOrDefault( m => m.Eat ) != null)
				{
					_UniqueForMove = lastPieceMoved;
					change_turn = false;
				}
			}

			if( change_turn )
			{
				_UniqueForMove = null;
				Turn = ( Turn == TeamEnum.WHITE ) ? TeamEnum.BLACK : TeamEnum.WHITE;
			}
				
			return Turn;
		}

		public int Evaluate()
		{
			int res = 0;

			TeamEnum winner = Winner();

			if( winner == TeamEnum.NONE )
			{

				int whites = Pieces.Where( a => !a.IsDeath && a.Team == TeamEnum.WHITE ).Sum( b => b.Value ) * ( int )TeamEnum.WHITE;
				int blacks = Pieces.Where( a => !a.IsDeath && a.Team == TeamEnum.BLACK ).Sum( b => b.Value ) * ( int )TeamEnum.BLACK;
				res += whites + blacks;

				// count pieces in the walls
				/*IEnumerable<IPiece> OnTheWall = Pieces.Where( a => !a.IsDeath &&
				                                ( a.Coordinates.x == 0
				                                || a.Coordinates.y == 0
				                                || a.Coordinates.x == MaxCoordinates.x - 1
				                                || a.Coordinates.y == MaxCoordinates.y - 1 ) );

				whites = OnTheWall.Count( a => a.Team == TeamEnum.WHITE ) * (int) TeamEnum.WHITE;
				blacks = OnTheWall.Count( a => a.Team == TeamEnum.BLACK ) * ( int )TeamEnum.BLACK;
				*/
				whites = Pieces.Count( a => !a.IsDeath && a.Team == TeamEnum.WHITE && ( a.Coordinates.x == 0
				|| a.Coordinates.y == 0
				|| a.Coordinates.x == MaxCoordinates.x - 1
				|| a.Coordinates.y == MaxCoordinates.y - 1 ) ) * ( int )TeamEnum.WHITE;

				blacks = Pieces.Count( a => !a.IsDeath && a.Team == TeamEnum.BLACK && ( a.Coordinates.x == 0
					|| a.Coordinates.y == 0
					|| a.Coordinates.x == MaxCoordinates.x - 1
					|| a.Coordinates.y == MaxCoordinates.y - 1 ) ) * ( int )TeamEnum.BLACK;

				res += whites + blacks;
			}
			else
				res = (int.MaxValue -1) * ( int )winner;
				
			//return res * ( int )Turn;

			return res;
		}
						
		public TeamEnum Winner()
		{
			TeamEnum res = TeamEnum.NONE;

			if( _NeedtoCalcWinner )
			{
				int blancas = Pieces.Count( a => !a.IsDeath && a.Team == TeamEnum.WHITE );
				int negras = Pieces.Count( b => !b.IsDeath && b.Team == TeamEnum.BLACK );

				if( blancas == 0 )
					res = TeamEnum.BLACK;
				else if( negras == 0 )
					res = TeamEnum.WHITE;
				else
				{
					// Maybe a team has pieces alive but blocks and cant move, so the other team wins 
					IEnumerable<Movement> moves = GenerateMoves();

					/*
				if( Turn == TeamEnum.BLACK && Pieces.Count( a => !a.IsDeath && a.Team == TeamEnum.BLACK && a.PossibleMoves.Count() > 0 ) == 0 )
					res = TeamEnum.WHITE;
				else if( Turn == TeamEnum.WHITE && Pieces.Count( a => !a.IsDeath && a.Team == TeamEnum.WHITE && a.PossibleMoves.Count() > 0 ) == 0 )
					res = TeamEnum.BLACK;
				*/
					if( moves.Count() == 0 )
						res = Turn == TeamEnum.BLACK ? TeamEnum.WHITE : TeamEnum.BLACK;
				}

				_NeedtoCalcWinner = false;
				_lastWinner = res;
			}

			return _lastWinner;
		}

		public Int64 GetHash()
		{
			throw new NotImplementedException();
		}

		#endregion

		#region private

		/// <summary>
		/// False when Movements is just generates
		/// </summary>
		private bool _NeedToGenerateMoves = true;
		private bool _NeedtoCalcWinner = true;
		private Movement _lastMovement = null;
		private TeamEnum _lastWinner = TeamEnum.NONE;
		private IPiece _UniqueForMove = null;	// When moves a piece and eat and can eat again, we save here
		private List<Movement> _Moves = new List<Movement>();
		private int _MovesToDraw;	// When reach 0 then is draw (20 moves without eat)

		private IEnumerable<Movement> SetPosibleMoves( IPiece p )
		{
			/* 
			 * RULES:
			 * Ends Coordinates must be empty
			 * If Eat and can eat again, must eat again.
			 * If no eat and exist a eat movement, is not valid.
			 * MAIDENS: Always forward and 1 square.
			 * QUEENS: Can move in any way and not necesary 1 square
			 */

			IEnumerable<Movement> res = null;

			if( p.Value == ( int )PieceType.MAIDEN )
			{
				res = MovesforMaidens( p );
			}
			else
			{
				res = MovesforQueens( p );
			}

			return res;
		}

		private IEnumerable<Movement> MovesforMaidens( IPiece p )
		{
			List<Movement> res = new List<Movement>();

			int avance = p.Team == TeamEnum.WHITE ? -1 : 1;
			int limit_for_crown = p.Team == TeamEnum.WHITE ? 0 : MaxCoordinates.y - 1;

			res = SimpleMovesForMaidens( p, avance, limit_for_crown ).ToList();
	
			// Optimizacion: If now we have 2 moves, then there is no need to check eat-moves
			if( res.Count() < 2 )
				res.AddRange( EatMovesForMaidens( p, avance, limit_for_crown ) );

			return res;
		}

		private IEnumerable<Movement> SimpleMovesForMaidens( IPiece p, int avance, int limit_for_crown )
		{
			List<Movement> res = new List<Movement>();

			Coordinate begin = p.Coordinates;
			Coordinate end = p.Coordinates;

			end.y = end.y + avance;
			end.x = end.x - 1;

			for( int i = 0; i < 2; ++i )
			{
				if( ValidCoordinates( end ) )
				{
					IPiece possibleeated = Pieces.FirstOrDefault( a => a.Coordinates.Equals( end ) && !a.IsDeath );
					if( possibleeated == null )
					{
						Movement m = new Movement() { Begin = begin, End = end, PieceMoved = p };

						if( end.y == limit_for_crown )
							m.TransformationMovement = true;

						res.Add( m );
					}
				}
				end.x = end.x + 2;
			}

			return res;
		}

		private IEnumerable<Movement> EatMovesForMaidens( IPiece p, int avance, int limit_for_crown )
		{
			List<Movement> res = new List<Movement>();

			Coordinate begin = p.Coordinates;
			Coordinate end = p.Coordinates;

			end.y = end.y + ( 2 * avance );
			end.x = end.x - 2;

			Coordinate intermedia = p.Coordinates;
			intermedia.x = intermedia.x - 1;
			intermedia.y = intermedia.y + avance;

			for( int i = 0; i < 2; ++i )
			{
				IPiece intermedia_piece = Pieces.FirstOrDefault( a => a.Coordinates.Equals( intermedia ) && !a.IsDeath );

				if( intermedia_piece != null && intermedia_piece.Team != p.Team )
				{
					if( ValidCoordinates( end ) )
					{
						IPiece emptysquare = Pieces.FirstOrDefault( a => a.Coordinates.Equals( end ) && !a.IsDeath  );
						if( emptysquare == null )
						{
							Movement m = new Movement() {
								Begin = begin,
								End = end,
								Eat = true,
								PieceMoved = p,
								PieceEated = intermedia_piece
								//PieceEatedCoords = intermedia_piece.Coordinates,
							};

							if( end.y == limit_for_crown )
								m.TransformationMovement = true;

							res.Add( m );
						}
					}
				}
				end.x = end.x + 4;
				intermedia.x = intermedia.x + 2;
			}

			return res;
		}

		private IEnumerable<Movement> MovesforQueens( IPiece p )
		{
			List<Movement> res = new List<Movement>();
			Coordinate begin = p.Coordinates;

			int avance_y = -1;
			int avance_x = -1;

			// 4 directions;
			for( int i = 0; i < 4; ++i )
			{
				if( i == 1 )
				{
					avance_y = -1;
					avance_x = 1;
				}
				else if( i == 2)
				{
					avance_y = 1;
					avance_x = -1;
				}
				else if( i == 3)
				{
					avance_y = 1;
					avance_x = 1;
				}

				int cuenta = 1;
				bool fin = false;

				Coordinate end = new Coordinate();
				end.x = p.Coordinates.x + ( cuenta * avance_x );
				end.y = p.Coordinates.y + ( cuenta * avance_y );

				bool enemy_finded = false;
				IPiece enemy_piece = null;

				while( !fin && ValidCoordinates( end ) )
				{
					IPiece intermedia_piece = Pieces.FirstOrDefault( a => a.Coordinates.Equals( end ) && !a.IsDeath );

					if( intermedia_piece == null )
					{
						Movement m = new Movement() 
						{
							Begin = begin,
							End = end,
							Eat = enemy_finded,
							PieceMoved = p,
							PieceEated = enemy_piece
							//PieceEatedCoords = enemy_finded ? enemy_piece.Coordinates : begin //enemy_piece.Coordinates
						};
								
						res.Add( m );
					}
					else
					{
						if( enemy_finded || intermedia_piece.Team == p.Team )
							fin = true;
						if( intermedia_piece.Team != p.Team )
						{
							enemy_finded = true;
							enemy_piece = intermedia_piece;
						}
					}

					++cuenta;
					end.x = p.Coordinates.x + ( cuenta * avance_x );
					end.y = p.Coordinates.y + ( cuenta * avance_y );
				}
			}

			return res;
		}

		private bool ValidCoordinates( Coordinate c )
		{
			return ( c.y >= 0 && c.y < MaxCoordinates.y && c.x >= 0 && c.x < MaxCoordinates.x );
		}
				
		#endregion
	}
}

